/*
 * Copyright 2007-2008 Michele Mostarda ( michele.mostarda@gmail.com ).
 * All Rights Reserved.
 * <p/>
 * Licensed under the Apache License, Version 2.0 (the 'License');
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * <p/>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p/>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an 'AS IS' BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */


package org.michelemostarda;

import java.io.*;
import java.util.Stack;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * This class convers a character stream in a HTML markup.
 */
public class Converter {

    /**
     * Stack marker for <i>italic</i> operator.
     */
    static class ItalicMarker extends Marker {

        private static final String ITALIC_TAG = "i";

        public boolean processStack(Converter converter, String lookahead, Stack stack, Emitter emitter) {
            if(removeMarker(stack) != null) {
                emitClosure(emitter);
            } else {
                emitter.emit("<" + ITALIC_TAG + ">");
                stack.push(new ItalicMarker());
            }
            return true; // mustLookAhead consumed.
        }

        public void emitClosure(Emitter emitter) {
            emitter.emit("</" + ITALIC_TAG + ">");
        }

        public boolean mustLookAhead(String s) {
            return false;
        }
    }

    /**
     * Stack marker for <i>bold</i> operator.
     */
    static class BoldMarker extends Marker {

        private static final String BOLD_TAG = "strong";

        public boolean processStack(Converter converter, String lookahead, Stack stack, Emitter emitter) {
            if(removeMarker(stack) != null) {
                emitClosure(emitter);
            } else {
                emitter.emit("<" + BOLD_TAG + ">");
                stack.push(new BoldMarker());
            }
            return true; // mustLookAhead consumed.
        }

        public void emitClosure(Emitter emitter) {
            emitter.emit("</" + BOLD_TAG +">");
        }

        public boolean mustLookAhead(String s) {
            return false;
        }
    }

    /**
     * Stack marker for <i>code</i> operator.
     */
    static class CodeMarker extends Marker {

        private static final String CODE_TAG = "tt";

        public boolean processStack(Converter converter, String lookahead, Stack stack, Emitter emitter) {
            if(removeMarker(stack) != null) {
                emitClosure(emitter);
            } else {
                emitter.emit("<" + CODE_TAG + ">");
                stack.push(new CodeMarker());
            }
            return true; // mustLookAhead consumed.
        }

        public void emitClosure(Emitter emitter) {
            emitter.emit("</" + CODE_TAG +">");
        }

        public boolean mustLookAhead(String s) {
            return false;
        }
    }

    /**
     * Stack marker for <i>super</i> operator.
     */
    static class SuperMarker extends Marker {

        private static final String SUPER_TAG = "sup";

        public boolean processStack(Converter converter, String lookahead, Stack stack, Emitter emitter) {
            if(removeMarker(stack) != null) {
                emitClosure(emitter);
            } else {
                emitter.emit("<" + SUPER_TAG + ">");
                stack.push(new SuperMarker());
            }
            return true; // mustLookAhead consumed.
        }

        public void emitClosure(Emitter emitter) {
            emitter.emit("</" + SUPER_TAG +">");
        }

        public boolean mustLookAhead(String s) {
            return false;
        }
    }

    /**
     * Stack marker for <i>sub</i> operator.
     */
    static class SubMarker extends Marker {

        private static final String SUB_TAG = "sub";

        public boolean processStack(Converter converter, String lookahead, Stack stack, Emitter emitter) {
            if(lookahead.length() != 2) {
                return false;
            }

            if(removeMarker(stack) != null) {
                emitClosure(emitter);
            } else {
                emitter.emit("<" + SUB_TAG + ">");
                stack.push(new SubMarker());
            }
            return true; // mustLookAhead consumed.
        }

        public void emitClosure(Emitter emitter) {
            emitter.emit("</" + SUB_TAG +">");
        }

        public boolean mustLookAhead(String s) {
            return isSequenceOf(s, SUB_CHAR);
        }
    }

    /**
     * Stack marker for <i>strike</i> operator.
     */
    static class StrikeMarker extends Marker {

        private static final String STRIKE_TAG = "strike";

        public boolean processStack(Converter converter, String lookahead, Stack stack, Emitter emitter) {
            if(lookahead.length() != 2) {
                return false;
            }

            if(removeMarker(stack) != null) {
                emitClosure(emitter);
            } else {
                emitter.emit("<" + STRIKE_TAG + ">");
                stack.push(new StrikeMarker());
            }
            return true; // mustLookAhead consumed.
        }

        public void emitClosure(Emitter emitter) {
            emitter.emit("</" + STRIKE_TAG +">");
        }

        public boolean mustLookAhead(String s) {
            return isSequenceOf(s, STRIKE_CHAR);
        }
    }

    /**
     * Stack marker for <i>title</i> operator.
     */
    static class TitleMarker extends Marker {

        private static final String HEAD_TAG = "h";

        private int level;

        TitleMarker() {}

        private TitleMarker(int l) {
            level = l;
        }

        boolean processStack(Converter converter, String lookahead, Stack stack, Emitter emitter) {
            // Close title.
            if(! stack.isEmpty() && stack.peek() instanceof TitleMarker) {

                int closingTitleLevel = ((TitleMarker) stack.pop()).level;
                emitter.emit("</" + HEAD_TAG + closingTitleLevel + ">");

            } else { // Open title.

                emitter.emit("<" + HEAD_TAG + lookahead.length() + ">");
                stack.push(new TitleMarker(lookahead.length()));

            }
            return true; // mustLookAhead consumed.
        }

        void emitClosure(Emitter emitter) {
            emitter.emit("</" + HEAD_TAG + level + ">");
        }

        boolean mustLookAhead(String s) {
            return isSequenceOf(s, TITLE_CHAR);
        }
    }

    /**
     * Stack marker for <i>escape</i> operator.
     */
    static class VerbatimMarker extends Marker {

        private static final String OPEN_VERBATIM = "<pre><code>";
        private static final String CLOSE_VERBATIM = "</code></pre>";

        boolean processStack(Converter converter, String lookahead, Stack stack, Emitter emitter) {
            // Check wheter receiving a '{{{' or '}}}'.
            if(lookahead.length() != 3) {
                return false; // mustLookAhead NOT consumed.
            }

            // Escape closure.
            if( ! stack.isEmpty() && stack.peek() instanceof VerbatimMarker && "}}}".equals(lookahead)) {
                converter.setEscape(false);
                emitter.setEncoding(false);
                emitter.setAddSpace(true);
                stack.pop();
                emitter.emit(CLOSE_VERBATIM);
                return true;
            } else if("{{{".equals(lookahead)) { // Escape opening.
                emitter.emit(OPEN_VERBATIM);
                stack.push( new VerbatimMarker() );
                emitter.setAddSpace(true);
                emitter.setEncoding(true);
                converter.setEscape(true);
                return true;
            }
            return false; // mustLookAhead consumed.
        }

        void emitClosure(Emitter emitter) {
            emitter.setEncoding(false);
            emitter.emit(CLOSE_VERBATIM);
        }

        boolean mustLookAhead(String s) {
            return isSequenceOf(s, VERBATIM_OPEN_CHAR) || isSequenceOf(s, VERBATIM_CLOSE_CHAR);
        }
    }

    /**
     * Stack marker for <i>divider</i> operator.
     */
    static class DividerMarker extends Marker {

        public boolean processStack(Converter converter, String lookahead, Stack stack, Emitter emitter) {
            if(lookahead.length() < 4) {
                return false;
            }

            emitter.emit("<hr/>");
            return true; // look ahead consumed.
        }

        public void emitClosure(Emitter emitter) {
            // Do nothing.
        }

        public boolean mustLookAhead(String s) {
            return isSequenceOf(s, DIVIDER_CHAR);
        }
    }

    /**
     * Stack marker for <i>link</i> operator.
     */
    static class LinkMarker extends Marker {

        static final String[] IMAGE_EXT = {".jpg", ".jpeg", ".png", ".gif"};

        private boolean isImage = false;

        private Pattern INTERNAL_LINK_PATTERN = Pattern.compile("[A-Z][a-z]*[A-Z][a-z]*");

        boolean processStack(Converter converter, String lookahead, Stack stack, Emitter emitter) {
            // Link closure.
            if( ! stack.isEmpty() && stack.peek() instanceof LinkMarker) {

                stack.pop();

                WordToken wt = (WordToken) emitter.removeElem(0);
                // If link content is empty aborts the operation.
                if(wt == null) {

                    emitter.setBufferize(false);
                    emitter.print("[]");

                } else { // Prints link content.

                    // Link is image.
                    if( (isImage = checkImage(wt.getText())) ) {
                        emitter.print("<img src=\"" + wt.getText() + "\">");
                    } else {
                        emitter.print("<a href=\"" + wt.getText() + "\">");

                    }
                    emitter.setBufferize(false);
                    //converter.setEscape(false); // Disabled by the LinkURLWordTokenHandler.
                    emitClosure(emitter);
                }

            } else { // Link opening.
                emitter.setBufferize(true);
                converter.setEscape(true);
                converter.wordTokenHandler = LINK_URL_WORD_TOKEN_HANDLER;
                stack.push(new LinkMarker());
            }
            return true; // look ahead consumed.
        }

        void emitClosure(Emitter emitter) {
            if(isImage) {
                emitter.emit("</img>");
            } else {
                emitter.emit("</a>");
            }
        }

        boolean mustLookAhead(String s) {
            return false;
        }

        boolean checkImage(String link) {
            for(String suffix : IMAGE_EXT) {
                if(link.indexOf(suffix) == link.length() - suffix.length()) {
                    return true;
                }
            }
            return false;
        }

        boolean checkInternalLink(String link) {
           Matcher m = INTERNAL_LINK_PATTERN.matcher(link);
           return m.find();
        }
    }

    /**
     * Stack marker for <i>table</i> operator.
     */
    static class TableMarker extends Marker {

        private boolean tableRowOpen  = false;

        TableMarker() {}

        TableMarker(boolean tro) {
            tableRowOpen  = tro;
        }

        boolean processStack(Converter converter, String lookahead, Stack stack, Emitter emitter) {
            if( ! lookahead.equals("||") ) {
                return false;
            }

            // Remaining part of table handling is coded inside </i>CarriageReturnMarker</i>.
            TableMarker tt = (TableMarker) findMarker(stack);
            // Close table.
            if(tt != null) {

                if(tt.tableRowOpen) {
                    emitter.emit("<td>");
                    tt.tableRowOpen = false;
                } else {
                    emitter.emit("</td><td>");
                }

            } else { // Open table.

                emitter.emit("<table><tr><td>");
                stack.push( new TableMarker(false) );

            }

            return true;
        }

        void emitClosure(Emitter emitter) {
           emitter.emit("</tr></table>");
        }

        boolean mustLookAhead(String s) {
            return isSequenceOf(s, TABLE_CHAR);
        }
    }

    /**
     * Stack marker for (enumerated) list operator.
     */
    static class ListMarker extends Marker {

        private boolean orderedList;

        ListMarker(boolean ol) {
            orderedList = ol;
        }

        ListMarker() {
            this(false);
        }

        boolean processStack(Converter converter, String lookahead, Stack stack, Emitter emitter) {
            char lastLAChar = lookahead.charAt(lookahead.length()-1);
            final boolean isOrderdered;
            if(lastLAChar == '#') { // Ordered list found.
                isOrderdered = true;
            } else if(lastLAChar == '*') { // Unordered list found.
                isOrderdered = false;
            } else {
                return false;
            }

            // Check previous opening of the list.
            ListMarker lm = (ListMarker) findMarker( stack, this.getClass() );
            if(lm == null) { // Need to open the list.
                emitter.emit( isOrderdered ? "<ol>" : "<ul>");
                stack.push( new ListMarker(isOrderdered) );
            }

            // Open the list item. The list item closure will handled by Carriage Return.
            emitter.emit("<li>");

            return true;
        }

        void emitClosure(Emitter emitter) {
            emitter.emit(orderedList ? "</ol>" : "</ul>");
        }

        boolean mustLookAhead(String lookAhead) {
            return isSequenceOf(lookAhead, ' ');
        }
    }

    /**
     * Stack marker for <i>carriage return</i> operator.
     */
    static class CarriageReturnMarker extends Marker {

        boolean processStack(Converter converter, String lookahead, Stack stack, Emitter emitter) {

            // Single carriage return handling.
            if(lookahead.length() == 1) {

                //TODO: write a generic pop handler to manage closures of a single item.

                // Table handling. Single CR means end of table row.
                TableMarker tm = (TableMarker) findMarker(stack, TableMarker.class);
                if(tm != null) {
                    emitter.emit("</tr><tr>");
                    tm.tableRowOpen = true;
                }

                // List handling. Single CR means end of list item.
                ListMarker lm = (ListMarker) removeMarker(stack, ListMarker.class);
                if(lm != null) {
                    emitter.emit("</li>");
                }

            } else { // Multi carriage return handling.

                // Table handling. Multi CR means end of table.
                TableMarker tt = (TableMarker) removeMarker(stack, TableMarker.class);
                if(tt != null) {
                    emitter.emit("</tr></table>");
                }

                converter.lookahead = false; // Prevents cycle.
                converter.closeMarkersStack();

                if(converter.encloseInParagraphs) {
                    emitter.emit("</p><p>");
                } else {
                    emitter.emit("<br/>");
                }
            }
            return true;
        }

        void emitClosure(Emitter emitter) {
            // Do nothing.
        }

        boolean mustLookAhead(String s) {
            return isSequenceOf(s, CARRIAGE_RETURN_CHAR);
        }
    }


    /**
     * Italic operator char.
     */
    protected static char ITALIC_CHAR = '_';

    /**
     * Bold operator char.
     */
    protected static char BOLD_CHAR = '*';

    /**
     * Code operator char.
     */
    protected static char CODE_CHAR = '`';

    /**
     * Super operator char.
     */
    protected static char SUPER_CHAR = '^';

    /**
     * Sub operator char.
     */
    protected static char SUB_CHAR = ',';

    /**
     * Strike operator char.
     */
    protected static char STRIKE_CHAR = '~';

    /**
     * Title operator char.
     */
    protected static char TITLE_CHAR = '=';

    /**
     * Escape opening operator char.
     */
    protected static char VERBATIM_OPEN_CHAR = '{';

    /**
     * Escape closing operator char.
     */
    protected static char VERBATIM_CLOSE_CHAR = '}';

    /**
     * Divider operator char.
     */
    protected static char DIVIDER_CHAR = '-';

    /**
     * Link open operator char.
     */
    protected static char LINK_OPEN_CHAR  = '[';

    /**
     * Link closed operator char.
     */
    protected static char LINK_CLOSE_CHAR = ']';

    /**
     * Table operator char.
     */
    protected static char TABLE_CHAR = '|';

    /**
     * List operator char.
     */
    protected static char LIST_CHAR = ' ';

    /**
     * Carriage return operator char.
     */
    protected static char CARRIAGE_RETURN_CHAR = '\n';


    public enum OPERATOR {

        /**
         * <tt>_italic text_</tt>
         */
        ITALIC {

            Marker getMarker(char c) {
                return ITALIC_CHAR == c ? new ItalicMarker() : null;
            }

        },

        /**
         * <tt>*bold text*</tt>
         */
        BOLD {

            Marker getMarker(char c) {
                return BOLD_CHAR == c ? new BoldMarker() : null;
            }

        },

        /**
         * <tt>`code text`</tt>
         */
        CODE {

            Marker getMarker(char c) {
                return CODE_CHAR == c ? new CodeMarker() : null;
            }

        },

        /**
         * <tt>^super text^</tt>
         */
        SUPER {

            Marker getMarker(char c) {
                return SUPER_CHAR == c ? new SuperMarker() : null;
            }

        },

        /**
         * <tt>,,sub text,,</tt>
         */
        SUB {

            Marker getMarker(char c) {
                return SUB_CHAR == c ? new SubMarker() : null;
            }

        },

        /**
         * <tt>~~strike text~~</tt>
         */
        STRIKE {

            Marker getMarker(char c) {
                return STRIKE_CHAR == c ? new StrikeMarker() : null;
            }

        },

        /**
         * <tt>==title text==</tt>
         */
        TITLE {

            Marker getMarker(char c) {
                return TITLE_CHAR == c ? new TitleMarker() : null;
            }

        },

        /**
         * <tt>{{{verbatim text}}}</tt>
         */
        VERBATIM {

            Marker getMarker(char c) {
                return (VERBATIM_OPEN_CHAR == c || VERBATIM_CLOSE_CHAR == c ? new VerbatimMarker() : null);
            }
        },

        /**
         * <tt>divider ----</tt>
         */
        DIVIDER {

            Marker getMarker(char c) {
                return (DIVIDER_CHAR == c ? new DividerMarker() : null);
            }
        },

        /**
         * <tt>[url description]</tt>
         */
        LINK {

            Marker getMarker(char c) {
                return LINK_OPEN_CHAR == c || LINK_CLOSE_CHAR == c ? new LinkMarker() : null;
            }

        },

        /**
         * <tt>||table||column||</tt>
         */
        TABLE {

            Marker getMarker(char c) {
                return TABLE_CHAR == c ? new TableMarker() : null;
            }
        },

        /**
         * pre text
         *   # ordered list item
         *   * unordered list item
         */
        LIST {

            Marker getMarker(char c) {
                if( LIST_CHAR == c ) {
                    System.out.println("");
                }
                return LIST_CHAR == c ? new ListMarker() : null;
            }
        },

        /**
         * Carriage return.
         */
        CARRIAGE_RETURN {

            Marker getMarker(char c) {
                return CARRIAGE_RETURN_CHAR == c ? new CarriageReturnMarker() : null;
            }
        };

        abstract Marker getMarker(char c);

    }

    /**
     * The word separator char.
     */
    private static final char WORD_SEPARATOR = ' ';

    /**
     * The comment operator.
     */
    private static final char COMMENT_CHAR   = '#';


    /**
     * Returns the operator associted to the given char if any,
     * <code>null</code> otherwise.
     * @param c
     * @return
     */
    Marker getOperator(char c) {

        // If escaping will be checked only the VERBATIM operator closure.
        if(escape) {
            return OPERATOR.VERBATIM.getMarker(c);
        }

        Marker result;
        for(OPERATOR o : OPERATOR.values()) {
            result = o.getMarker(c);
            if(result != null) {
                return result;
            }
        }
        return null;
    }

    /**
     * Default word token handler.
     */
    static private class DefualtWordTokenHandler implements WordTokenHandler {

        public void processWord(Converter converter, Emitter emitter, StringBuffer wordBuffer) {
            if(wordBuffer.length() > 0) {
                emitter.emit(wordBuffer.toString());
                wordBuffer.delete(0, wordBuffer.length());
            }
        }
    }

    /**
     * Handles the first word of a link, containing the URL.
     */
    static private final class LinkURLWordTokenHandler extends DefualtWordTokenHandler {

        public void processWord(Converter converter, Emitter emitter, StringBuffer wordBuffer) {
            super.processWord(converter, emitter, wordBuffer);
            // Disables the escaping at the end of the first word.
            converter.setEscape(false);
            // Ripristinate the default word processor.
            converter.wordTokenHandler = DEFUALT_WORD_TOKEN_HANDLER;
        }
    }

    protected static final PageAttributes DEFALT_PAGE_ATTRIBUTES = new PageAttributes("<no title>", "<no author>");

    /**
     * The attributes associated to the current input stream.
     */
    private PageAttributes attributes;

    /**
     * Internal emitter instance.
     */
    private Emitter emitter;

    /**
     * Word buffer, contains the words of the text.
     */
    private StringBuffer word = new StringBuffer();

    /**
     * Stack of the operators opened and not yet closed.
     */
    private Stack<Marker> markersStack = new Stack<Marker>();

    /**
     * If <code>true</code> the current line is a comment and will be skipped.
     */
    private boolean skipLine = false;

    /**
     * Current operator marker if any, <code>null</code>otherwise.
     */
    private Marker operatorMarker;

    /**
     * Look ahead mode is active, if so a consecutive sequence of same characters is collected
     * until a different one is reached.
     */
    private boolean lookahead = false;

    /**
     * Buffer containing the look ahead.
     */
    private StringBuffer lookAheadBuffer;

    /**
     * If <code>true</code>, the meta characted are not considered (excluding the escape char).
     */
    private boolean escape = false;

    /**
     * The default word token handler.
     */
    private static final WordTokenHandler DEFUALT_WORD_TOKEN_HANDLER = new DefualtWordTokenHandler();

    /**
     * The link URL word token handler.
     */
    private static final WordTokenHandler LINK_URL_WORD_TOKEN_HANDLER = new LinkURLWordTokenHandler();

    /**
     * Processes a word to give it a different meaning.
     */
    private WordTokenHandler wordTokenHandler = DEFUALT_WORD_TOKEN_HANDLER;

    /**
     * If <code>true</code>, every code block will be enclosed into a paragraph.
     */
    private boolean encloseInParagraphs = false;

    /**
     * Constructor.
     *
     * @param e
     */
    public Converter(Emitter e) {
        attributes = DEFALT_PAGE_ATTRIBUTES;
        lookAheadBuffer = new StringBuffer();
        emitter = e;
    }

    /**
     * Constructor.
     */
    public Converter() {
       this(new Emitter());
    }

    /**
     * Sets the page attributes.
     *
     * @param pa
     */
    public void setAttributes(PageAttributes pa) {
        if(pa == null) {
            attributes = DEFALT_PAGE_ATTRIBUTES;
        }
        attributes = pa;
    }

    /**
     * Returns the page attributes.
     *
     * @return
     */
    public PageAttributes getAttributes() {
        return attributes;
    }

    /**
     * Returns the converter emitter.
     * 
     * @return
     */
    public Emitter getEmitter() {
        return emitter;
    }

    protected void setEscape(boolean e) {
        escape = e; 
    }

    protected void processChar(char c) {

        // Look ahead management.
        if(lookahead) {

            // If expected character.
            if(operatorMarker.mustLookAhead( lookAheadBuffer.toString() + c)) {

                lookAheadBuffer.append(c);
                return;

            } else {

                lookahead = false;

                // Prints the chars of the look ahead.
                if( ! operatorMarker.processStack(this, lookAheadBuffer.toString(), markersStack, emitter) ) {
                    emitter.emit(lookAheadBuffer.toString());
                }
                
            }
        }

        // Comment handler.
        if(skipLine) {
            if( c == '\n' ) {
                skipLine = false;
            } else {
                return;
            }
        } else if(COMMENT_CHAR == c) {
            skipLine = true;
            return;
        }

        // Operator handler.
        if( ( operatorMarker = getOperator(c) ) != null ) {

              processWord();

            // handle the look ahead.
            if( operatorMarker.mustLookAhead(Character.toString(c)) ) {
                lookahead = true;
                lookAheadBuffer.delete(0, lookAheadBuffer.length());
                lookAheadBuffer.append(c);
            }else {
                if( ! operatorMarker.processStack(this, Character.toString(c), markersStack, emitter) ) {
                    emitter.emit(Character.toString(c));
                }
            }

        // Space handler.
        } else if(WORD_SEPARATOR == c) {

            processWord();

        // Word handler.
        } else {

            word.append(c);
            
        }
    }

    /**
     * Processes the current word.
     */
    protected void processWord() {
        wordTokenHandler.processWord(this, emitter, word);
    }

    /**
     * Finalizes the stack content generating closures for every marker.
     */
    protected void closeMarkersStack() {
        // Flushes the word buffer.
        processWord();

        // If looking ahead the mustLookAhead chars must be consumed.
        if(lookahead) {
            // Flushes the look ahead if last stack processing didn't consume it.
            if( ! operatorMarker.processStack(this, lookAheadBuffer.toString(), markersStack, emitter) ) {
                emitter.emit(lookAheadBuffer.toString());
            }
        }

        // Emit all remaining closures in the stack.
        Marker marker;
        while( ! markersStack.isEmpty() ) {
             marker = markersStack.pop();
            marker.emitClosure(emitter);
        }

        // Disable eventual escaping.
        setEscape(false);
    }

    /**
     * Cleans the converter internal state.
     */
    public void clear() {
        word.delete(0, word.length());
        markersStack.clear();
        skipLine = false;
        operatorMarker = null;
        lookahead = false;
        lookAheadBuffer.delete(0,lookAheadBuffer.length());
        escape = false;
        wordTokenHandler = DEFUALT_WORD_TOKEN_HANDLER;
        encloseInParagraphs = false;
    }

    /**
     * Parses an input stream generating output on the emitter.
     *
     * @param inputStream
     * @throws IOException
     */
    public void parse(InputStream inputStream) throws IOException {
        if(inputStream == null) {
            throw new IllegalArgumentException();
        }

        try {
            int ic;
            char c;
            while( ( ic = inputStream.read() ) != -1 ) {
                c = (char) ic;
                processChar(c);
            }
            closeMarkersStack();
        } finally {
            clear();
        }
    }

    /**
     * Parses a file unit.
     * 
     * @param file
     * @throws IOException
     */
    public void parseUnit( File file, OutputStream outputStream) throws IOException {
        if(file == null || outputStream == null) {
            throw new IllegalArgumentException();
        }
        FileInputStream fis = new FileInputStream(file);
        PrintStream ps = new PrintStream(outputStream);

        encloseInParagraphs = true;

        printPreamble(ps);
        parse(fis);
        printClosure(ps);
    }

    /**
     * Parses a file unit.
     * 
     * @param file
     * @throws IOException
     */
    public void parseUnit(File file) throws IOException {
        parseUnit( file, System.out );
    }

    private void printPreamble(PrintStream ps) {
        ps.println(
                "<html>" +
                    "<head>" +
                        "<meta name=\"author\" content=\"" + getAttributes().getAuthor() + "\"/>" +
                        "<title>" + getAttributes().getTitle() +"</title>" +
                        "<script type=\"text/javascript\" src=\"highlight.js\"></script>\n" +
                        "<script type=\"text/javascript\">\n" +
                        "   hljs.initHighlightingOnLoad.apply(null, hljs.ALL_LANGUAGES);\n" +
                        "</script>"+
                    "</head>" +
                    "<body>" +
                        "<p>"
        );
    }

    private void printClosure(PrintStream ps) {
        ps.println(
                        encloseInParagraphs  ? "</p>" : "" +
                     "</body>" +
                "</html>"
        );
    }


}
